package dbMysql

import (
	"bytes"
	"fmt"
	"gitee.com/kingzyt/common/log"
	"reflect"
	"strconv"
	"strings"
)

type TableDescription struct {
	TableName            string
	ColumnNames          []string
	ColumnTypes          []reflect.Kind
	ColumnAutoIncrements []bool
	ColumnScanFaces      []interface{} // 因为scanfaces需要slice结构来直接传入mysql读写接口, 所以为了配合这个,其他结构也独立slice比较统一
}

func GetMysqlTableDescription(tableName string, sqlRWer *SimpleMySqlRWer) (queryDesc *TableDescription, err error) {
	queryDesc = &TableDescription{TableName: tableName}

	var columnName string
	var columnType string
	var columnExtra string

	err = sqlRWer.ReadQueryRows(
		fmt.Sprintf("select COLUMN_NAME,COLUMN_TYPE, EXTRA from information_schema.columns where TABLE_SCHEMA=\"%s\" and TABLE_NAME=\"%s\";", sqlRWer.GetDBName(), queryDesc.TableName),
		[]interface{}{&columnName, &columnType, &columnExtra}, func(rowScanFace []interface{}) {
			queryDesc.ColumnNames = append(queryDesc.ColumnNames, columnName)
			columnType = strings.ToLower(columnType)
			var t reflect.Kind
			if strings.Contains(columnType, "int") {
				if strings.Contains(columnType, "unsigned") {
					v := uint64(0)
					t = reflect.Uint64
					queryDesc.ColumnScanFaces = append(queryDesc.ColumnScanFaces, &v)
				} else {
					v := int64(0)
					t = reflect.Int64
					queryDesc.ColumnScanFaces = append(queryDesc.ColumnScanFaces, &v)
				}
			} else if strings.Contains(columnType, "char") || strings.Contains(columnType, "text") || strings.Contains(columnType, "json") {
				v := []byte{}
				queryDesc.ColumnScanFaces = append(queryDesc.ColumnScanFaces, &v)
				t = reflect.String
			} else if strings.Contains(columnType, "float") || strings.Contains(columnType, "double") {
				v := float64(0)
				queryDesc.ColumnScanFaces = append(queryDesc.ColumnScanFaces, &v)
				t = reflect.Float64
			} else if strings.Contains(columnType, "blob") || strings.Contains(columnType, "binary") {
				v := []byte{}
				queryDesc.ColumnScanFaces = append(queryDesc.ColumnScanFaces, &v)
				t = reflect.Slice
			} else if strings.Contains(columnType, "date") || strings.Contains(columnType, "time") {
				v := []byte{}
				queryDesc.ColumnScanFaces = append(queryDesc.ColumnScanFaces, &v)
				t = reflect.String
			} else {
				t = reflect.Invalid

				log.Error(tableName, "colum type [%s] is not supported", columnType)
			}
			if t != reflect.Invalid {
				queryDesc.ColumnTypes = append(queryDesc.ColumnTypes, t)
				queryDesc.ColumnAutoIncrements = append(queryDesc.ColumnAutoIncrements, strings.Contains(strings.ToLower(columnExtra), "auto_increment"))
			}
		})
	return
}

func GetDatabasesAllTableNames(sqlRWer *SimpleMySqlRWer, likePattern string) (tableNames []string, err error) {
	query := "show tables"
	if likePattern != "" {
		query += " like '" + likePattern + "';"
	} else {
		query += ";"
	}
	var tableName string
	err = sqlRWer.ReadQueryRows(query, []interface{}{&tableName}, func(rowScanFace []interface{}) {
		tableNames = append(tableNames, tableName)
	})
	return
}

func GetMysqlTableDescriptions(name string, likePattern string, sqlRWer *SimpleMySqlRWer) (queryDescs []*TableDescription, err error) {
	tableNames, err := GetDatabasesAllTableNames(sqlRWer, likePattern)
	if err != nil {
		return
	}

	if len(tableNames) <= 0 {
		return nil, nil
	}

	for _, tableName := range tableNames {
		var queryDesc *TableDescription
		queryDesc, err = GetMysqlTableDescription(tableName, sqlRWer)
		if err != nil {
			return
		}
		queryDescs = append(queryDescs, queryDesc)
	}
	return
}

func CheckTablesDescriptionSame(module string, queryDescs []*TableDescription) bool {
	if len(queryDescs) <= 0 {
		return true
	}

	compareBase := queryDescs[0]
	for i := 1; i < len(queryDescs); i++ {
		qd := queryDescs[i]
		if len(compareBase.ColumnNames) != len(qd.ColumnNames) {
			log.Error(module, "table %s struct is different with %s", qd.TableName, compareBase.TableName)
			return false
		}

		for k := 0; k < len(compareBase.ColumnNames); k++ {
			if compareBase.ColumnNames[k] != qd.ColumnNames[k] ||
				compareBase.ColumnTypes[k] != qd.ColumnTypes[k] ||
				compareBase.ColumnAutoIncrements[k] != qd.ColumnAutoIncrements[k] {
				log.Error(module, "table %s struct is different with %s", qd.TableName, compareBase.TableName)
				return false
			}
		}
	}
	return true
}

func ViewTableDatas(tableName string, sqlRWer *SimpleMySqlRWer, whereConds string, sortRow string, desc bool, limit int, procRow func(columnNames []string, cols []interface{})) (columnNames []string, err error) {
	queryDesc, err := GetMysqlTableDescription(tableName, sqlRWer)
	if err != nil {
		return
	}

	columnNames = queryDesc.ColumnNames

	query := &bytes.Buffer{}
	query.WriteString("select ")
	for i := 0; i < len(queryDesc.ColumnNames); i++ {
		query.WriteString(queryDesc.ColumnNames[i])
		if i < len(queryDesc.ColumnNames)-1 {
			query.WriteString(",")
		}
	}
	query.WriteString(" from ")
	query.WriteString(queryDesc.TableName)
	if whereConds != "" {
		query.WriteString(" where ")
		query.WriteString(whereConds)
	}
	if sortRow != "" {
		query.WriteString(" order by ")
		query.WriteString(sortRow)
		if desc {
			query.WriteString(" desc")
		}
	}
	if limit > 0 {
		query.WriteString(" limit ")
		query.WriteString(strconv.Itoa(limit))
	}
	query.WriteString(";")

	//fmt.Println(query)
	cols := make([]interface{}, len(queryDesc.ColumnScanFaces))

	err = sqlRWer.ReadQueryRows(query.String(), queryDesc.ColumnScanFaces, func(rowScanFace []interface{}) {
		for i := 0; i < len(queryDesc.ColumnScanFaces); i++ {
			sf := queryDesc.ColumnScanFaces[i]
			t := queryDesc.ColumnTypes[i]
			switch t {
			case reflect.Uint64:
				cols[i] = *(sf.(*uint64))
			case reflect.Int64:
				cols[i] = *(sf.(*int64))
			case reflect.String:
				cols[i] = string(*(sf.(*[]byte)))
			case reflect.Float64:
				cols[i] = *(sf.(*float64))
			case reflect.Slice:
				cols[i] = *(sf.(*[]byte))
			default:
				cols[i] = nil
			}
		}
		procRow(columnNames, cols)
	})
	return
}
